package de.vocabulary.server.persistence;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.sleepycat.bind.EntryBinding;
import com.sleepycat.bind.serial.SerialBinding;
import com.sleepycat.bind.serial.StoredClassCatalog;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;

/**
 * Convenience class which wraps a BerkeleyDB database using SerialBinding
 *
 */
public class BerkeleyDbDatabaseSerialBinding {
	
	protected Database db;
	protected Database classDb;
	protected StoredClassCatalog classCatalog;
	protected EntryBinding entryBinding;
	
	public static final String KEY_ENCODING = "UTF-8";
	
	/**
	 * Uses Strings as keys.
	 * @param environment
	 * @param dbName
	 * @param isReadOnly
	 * @param allowCreateNew
	 * @param entryBinding
	 * @throws DatabaseException
	 */
	public BerkeleyDbDatabaseSerialBinding(
			BerkeleyDbEnvironment env,
			Class<? extends Object> classToSerialize)
		throws DatabaseException
	{
		   // Environment open omitted for brevity

	    // Open the database that you will use to store your data
	    DatabaseConfig config = new DatabaseConfig();
	    config.setAllowCreate(true);
	    config.setSortedDuplicates(true);
	    db = env.getEnvironment().openDatabase(null, classToSerialize.getCanonicalName(), config);

	    // Open the database that you use to store your class information.
	    // The db used to store class information does not require duplicates
	    // support.
	    config.setSortedDuplicates(false);
	    classDb = env.getEnvironment().openDatabase(null, classToSerialize.getCanonicalName() + "_classDb", config); 

	    // Instantiate the class catalog
	    classCatalog = new StoredClassCatalog(classDb);

	    // Create the binding
	    entryBinding = new SerialBinding(classCatalog, classToSerialize);
	}
	
	public void put(String key, Object obj) throws UnsupportedEncodingException  {
	    // Create the DatabaseEntry for the key
	    DatabaseEntry dbKey = new DatabaseEntry(key.getBytes("UTF-8"));

	    // Create the DatabaseEntry for the data. Use the EntryBinding object
	    // that was just created to populate the DatabaseEntry
	    DatabaseEntry data = new DatabaseEntry();
	    entryBinding.objectToEntry(obj, data);

	    // Put it as normal
	    db.put(null, dbKey, data);
	}
		
	public Object get(String key) throws UnsupportedEncodingException  {
		// Create DatabaseEntry objects for the key and data
	    DatabaseEntry entryKey = new DatabaseEntry(key.getBytes("UTF-8"));
	    DatabaseEntry entryData = new DatabaseEntry();

	    // Do the get as normal
	    db.get(null, entryKey, entryData, LockMode.DEFAULT);

	    // Recreate the MyData object from the retrieved DatabaseEntry using
	    // the EntryBinding created above
	    return entryBinding.entryToObject(entryData);
	}
	
	
	/**
	 * Returns a cursor. Don't forget to close it.
	 * @return
	 * @throws DatabaseException
	 */
	public Cursor getCursor() throws DatabaseException {
		return db.openCursor(null, null);
	}
	
	/**
	 * Gets the keys of all stored entries.
	 * @return List of keys
	 * @throws DatabaseException
	 * @throws UnsupportedEncodingException
	 */
	public List<String> getKeys() throws DatabaseException, UnsupportedEncodingException {
		Cursor cursor = getCursor();
		DatabaseEntry foundKey = new DatabaseEntry();
	    DatabaseEntry foundData = new DatabaseEntry();

	    List<String> keys = new ArrayList<String>();
	    
	    try {
			while (cursor.getNext(foundKey, foundData, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
			    String keyString = new String(foundKey.getData(), KEY_ENCODING);
			    keys.add(keyString);
			}
		} finally {
			cursor.close();
		}
		
		return keys;
	}
	
	public Iterator<String> keyIterator() throws DatabaseException {
		return new BerkeleyDbKeyIterator();
	}
	
	public Iterator<Integer> idIterator() throws DatabaseException {
		return new BerkeleyDbIdIterator();
	}
		
	public boolean hasKey(String key) throws UnsupportedEncodingException {
	    DatabaseEntry entryKey = new DatabaseEntry(key.getBytes("UTF-8"));
	    DatabaseEntry entryData = new DatabaseEntry();

	    OperationStatus status = db.get(null, entryKey, entryData, LockMode.DEFAULT);
		
		if (status == OperationStatus.NOTFOUND) {
			return false;
		}
		else
		{
			return true;
		}
	}
	
	public String getContentInformation() throws DatabaseException, UnsupportedEncodingException {
		StringBuilder out = new StringBuilder();
		
		Cursor cursor = getCursor();
		DatabaseEntry foundKey = new DatabaseEntry();
	    DatabaseEntry foundData = new DatabaseEntry();

	    int i=0;
	    try {
			while (cursor.getNext(foundKey, foundData, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
			    String keyString = new String(foundKey.getData(), KEY_ENCODING);
			    Object obj = entryBinding.entryToObject(foundData);
			    out.append(i);
			    out.append(". Element\n");
			    out.append("key: \n");
			    out.append(keyString);
			    out.append("\nvalue: \n");
			    out.append(obj.toString());
			    out.append("\n");
			    i++;
			}
		} finally {
			cursor.close();
		}
		
		return out.toString();
	}
	
	public void close()  {
		if (db != null) {
        	try {
				db.close();
			} catch (DatabaseException e) {
				e.printStackTrace();
			}	
        }
		
		if (classDb != null) {
        	try {
				classDb.close();
			} catch (DatabaseException e) {
				e.printStackTrace();
			}	
        }
	}
	
	class BerkeleyDbKeyIterator implements Iterator<String> {
		private Cursor cursor;
		private String currentKey;
		
		DatabaseEntry foundKey = new DatabaseEntry();
	    DatabaseEntry foundData = new DatabaseEntry();
		
		public BerkeleyDbKeyIterator() throws DatabaseException {
			cursor = getCursor();
		}
		
		@Override
		public boolean hasNext()
		{
			try {
				if (cursor.getNext(foundKey, foundData, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
					currentKey = new String(foundKey.getData(), KEY_ENCODING);
					return true;
				}
				else {
					cursor.close();
					return false;
				}
			}
			catch (DatabaseException e) {
				e.printStackTrace();
				return false;
			}
			catch (UnsupportedEncodingException e) {
				e.printStackTrace();
				return false;
			}
		}

		@Override
		public String next()
		{
			return currentKey;
		}

		@Override
		public void remove()
		{
			// nothing
		}
	}
	
	class BerkeleyDbIdIterator implements Iterator<Integer> {
		private Cursor cursor;
		private Integer currentKey;
		
		DatabaseEntry foundKey = new DatabaseEntry();
	    DatabaseEntry foundData = new DatabaseEntry();
		
		public BerkeleyDbIdIterator() throws DatabaseException {
			cursor = getCursor();
		}
		
		@Override
		public boolean hasNext()
		{
			try {
				if (cursor.getNext(foundKey, foundData, LockMode.DEFAULT) == OperationStatus.SUCCESS) {
					currentKey = (Integer) entryBinding.entryToObject(foundKey);
					return true;
				}
				else {
					cursor.close();
					return false;
				}
			}
			catch (DatabaseException e) {
				e.printStackTrace();
				return false;
			}
		}

		@Override
		public Integer next()
		{
			return currentKey;
		}

		@Override
		public void remove()
		{
			throw new RuntimeException(new Throwable("remove() not yet implemented"));
		}
	}
}
